package IC.LIR;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import IC.AST.Field;
import IC.AST.ICClass;
import IC.AST.Method;
import IC.Symbols.MethodSymbol;
import IC.Symbols.Symbol;
import IC.Types.MethodType;
import IC.Types.Type;
import IC.Types.TypeTable;

public class ClassLayout {

	private Map<String, Integer> methodsOffsets;
	private Map<String, Integer> fieldsOffsets;
	private Map<String, Method> methodsAsts;
	private Map<String, String> methodsStringsForDV;
	private String className;
	private String dispatchVector;


	public ClassLayout(ICClass icClass, ClassLayout superClassOffsets) {
		className = icClass.getName();
		methodsOffsets = new LinkedHashMap<String, Integer>();
		fieldsOffsets = new LinkedHashMap<String, Integer>();
		methodsAsts = new LinkedHashMap<String, Method>();

		int index = 1;		
		List<Field> fields = icClass.getFields();
		if (superClassOffsets != null){
			fieldsOffsets.putAll(superClassOffsets.getFieldsOffsets());
			index = (fieldsOffsets.size() > 0 ? fieldsOffsets.size() : 1); 
		}

		for (int i = 0; i < fields.size(); i++, index++){
			fieldsOffsets.put(fields.get(i).getName(), index);				
		}

		
		methodsStringsForDV = new LinkedHashMap<String, String>();

		index = 0;
		if (superClassOffsets != null){
			methodsOffsets.putAll(superClassOffsets.getMethodsOffsets()); //add all functions of superclass
			methodsAsts.putAll(superClassOffsets.getMethodsAsts()); //add all method ASTs of superclass
			
			for (Method method : superClassOffsets.getMethodsAsts().values()){
				if (isMainMethod(method)){
					methodsStringsForDV.put("main", "_ic_main");
				} else{
					methodsStringsForDV.put(method.getName(), superClassOffsets.getMethodLabel(method.getName()));
				}
			}
			index = methodsOffsets.size();
		}

		List<Method> methods = icClass.getMethods();
		for (int i = 0; i < methods.size(); i++, index++){
			if (methodsOffsets.get(methods.get(i).getName()) == null){ //function isn't overriding another superclass function
				methodsOffsets.put(methods.get(i).getName(), index);
			}
			if (isMainMethod(methods.get(i))){
				methodsStringsForDV.put("main", "_ic_main");
			} else{
				methodsStringsForDV.put(methods.get(i).getName(),"_" + className + "_" +  methods.get(i).getName());
			}
			methodsAsts.put(methods.get(i).getName(), methods.get(i));
		}
		
		StringBuffer buffer = new StringBuffer();
		buffer.append("_DV_" + className + ": [");
		if (methodsStringsForDV.size() == 0){
			buffer.append("]\n");
		} else {
			for (String label : methodsStringsForDV.values()){
				buffer.append(label + ",");
			}
			buffer.deleteCharAt(buffer.length() - 1);
			buffer.append("]\n");
		}
		
		dispatchVector = buffer.toString();
	}


	public ClassLayout(ICClass icClass){
		this(icClass, null);
	}

	public int getMethodOffset(String methodName){
		return methodsOffsets.get(methodName);
	}

	public int getFieldOffset(String fieldName){
		return fieldsOffsets.get(fieldName);
	}

	public String getDispatchVector(){
		return dispatchVector;
	}

	public Map<String,Integer> getFieldsOffsets(){
		return fieldsOffsets;
	}

	public Map<String,Method> getMethodsAsts(){
		return methodsAsts;
	}
	
	public Method getMethodAst(String methodName){
		return methodsAsts.get(methodName);
	}
	
	public Map<String,Integer> getMethodsOffsets(){
		return methodsOffsets;
	}

	public int getNumOfBytesPerObject(){
		return (fieldsOffsets.size() + 1) * 4;
	}

	public String getDispatchVectorNameOnly(){
		return "_DV_" + className;
	}

	public String getMethodLabel(String methodName){
		return methodsStringsForDV.get(methodName);
	}
	
	public String getClassName(){
		return className;
	}
	
	private boolean isMainMethod(Method method) {

		if (!method.getName().toLowerCase().equals("main")){
			return false;
		}
		Symbol mainSymbol = method.getEnclosingScope().lookup(method.getName(), false);
		if ((mainSymbol != null && mainSymbol instanceof MethodSymbol) && (((MethodSymbol) mainSymbol).isStatic())){
			MethodSymbol mainMethodSymbol = (MethodSymbol) mainSymbol;
			MethodType type = (MethodType) mainMethodSymbol.getType();
			if (type.getReturnType() == TypeTable.getVoidType()){
				Type[] paramTypes = type.getParamTypes();
				if (paramTypes.length == 1){
					if (paramTypes[0] == TypeTable.arrayType(TypeTable.getStringType())){
						return true;
					}
				}
			}
		}		

		return false;
	}
}
